package io.jenkins.plugins.SubversionCommitterNotification;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.*;
import hudson.scm.ChangeLogSet;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Builder;
import hudson.util.FormValidation;
import jenkins.tasks.SimpleBuildStep;
import okhttp3.*;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.message.BasicNameValuePair;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.QueryParameter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.servlet.ServletException;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;

public class WorkWeiXinBuilder extends Builder implements SimpleBuildStep {

    private static final Logger log = LoggerFactory.getLogger(WorkWeiXinBuilder.class);

    public static class Authorization {
        private final String corpid;
        private final String corpsecret;

        private Date dateRequested;
        private String accessToken;
        private Integer expiredSeconds;
        private Integer reRequestCounter;

        private Authorization(String corpid, String corpsecret) {
            this.corpid = corpid;
            this.corpsecret = corpsecret;
            this.reRequestCounter = 0;
        }

        public String getCorpid() {
            return corpid;
        }

        public String getCorpsecret() {
            return corpsecret;
        }

        public String getToken() {
            if (null == dateRequested || null == expiredSeconds || new Date().getTime() - dateRequested.getTime() > (expiredSeconds - 10) * 1000) {
                String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=" + corpid + "&corpsecret=" + corpsecret;

                Request request = new Request.Builder().url(url).get().build();

                try {
                    dateRequested = new Date();
                    OkHttpClient client = new OkHttpClient();
                    Response response = client.newCall(request).execute();

                    log.debug("Invocation of webhook {} successful", url);
                    JSONObject jsonResp = JSON.parseObject(response.body().string());

                    accessToken = jsonResp.getString("access_token");
                    expiredSeconds = jsonResp.getIntValue("expires_in");
                    reRequestCounter++;
                    System.out.println(1);
                } catch (Exception e) {
                    log.info("Invocation of webhook {} failed", url, e);
                }
            }

            return accessToken;
        }

        public Integer getReRequestCounter() {
            return reRequestCounter;
        }
    }

    private final Authorization authorization;

    private final Integer agentid;
    private final String accountBindings;
    private final String recipients;

    private boolean allways;
    private boolean failed = true;
    private boolean sucessed;

    private final Map<String, String> accountMapper;
    private final List<String> defaultRecipients;

    @DataBoundConstructor
    public WorkWeiXinBuilder(String corpid, String corpsecret, String agentid, String accountBindings, String recipients) {
        this.authorization = new Authorization(corpid, corpsecret);

        if ("".equals(agentid))
            this.agentid = 0;
        else
            this.agentid = Integer.parseInt(agentid);

        this.accountBindings = accountBindings;
        this.recipients = recipients;

        this.accountMapper = new LinkedHashMap<>();
        this.defaultRecipients = new LinkedList<>();

        for (String binding : this.accountBindings.split(",")) {
            if (binding.contains("@")) {
                String[] binder = binding.split("@");//formatter:    svn-committer@tapd-userid
                this.accountMapper.put(binder[0], binder[1]);
            }
        }

        for (String recipient : this.recipients.split(",")) {
            this.defaultRecipients.add(recipient);
        }
    }

    @DataBoundSetter
    public void setAllways(boolean allways) {
        this.allways = allways;
    }

    @DataBoundSetter
    public void setFailed(boolean failed) {
        this.failed = failed;
    }

    @DataBoundSetter
    public void setSucessed(boolean sucessed) {
        this.sucessed = sucessed;
    }

    public boolean isAllways() {
        return allways;
    }

    public boolean isFailed() {
        return failed;
    }

    public boolean isSucessed() {
        return sucessed;
    }

    public String getCorpid() {
        return this.authorization.getCorpid();
    }

    public String getCorpsecret() {
        return this.authorization.getCorpsecret();
    }

    public Integer getAgentid() {
        return agentid;
    }

    public String getAccountBindings() {
        return accountBindings;
    }

    public String getRecipients() {
        return recipients;
    }

    @Override
    public void perform(@Nonnull Run<?, ?> run, @Nonnull FilePath workspace, @Nonnull Launcher launcher, @Nonnull TaskListener listener) throws InterruptedException, IOException {
        AbstractBuild build = (AbstractBuild) run;

        if (build.getChangeSet().isEmptySet())
            listener.getLogger().println("No notification was posted");
        else {
            List<String> authors = new LinkedList<>(defaultRecipients);
            StringBuilder contentBuilder = new StringBuilder("Jenkins Build Notification:");

            SimpleDateFormat formatter = new SimpleDateFormat("[YYYY-MM-dd HH:mm][");

            for (Object entry : build.getChangeSet()) {
                ChangeLogSet.Entry item = (ChangeLogSet.Entry) entry;

                String message = item.getMsg();
                String author = item.getAuthor().getFullName();
                String userId = accountMapper.get(author);

                if (userId != null) {
                    listener.getLogger().println("Message:" + message + ", Author:" + author + ", UserId:" + userId);
                    authors.add(author);
                } else
                    listener.getLogger().println("Message:" + message + ", Author:" + author + ", UserId Unbound");

                contentBuilder.append("\n" + formatter.format(new Date(item.getTimestamp())) + author + "]" + message + "[Revision#" + item.getCommitId() + "]");
            }

            if (!authors.isEmpty())
                sendWorkWeiXinMessage(authors, contentBuilder.toString());

            listener.getLogger().println("Notification was posted");
        }
    }

    Authorization getAuthorization() {
        return this.authorization;
    }

    @Symbol("greet")
    @Extension
    public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {

        public DescriptorImpl() {
            load();
        }

        public FormValidation doCheckCorpid(@QueryParameter String value)
                throws IOException, ServletException {
            if (value.length() == 0)
                return FormValidation.error("NotNull");

            return FormValidation.ok();
        }

        public FormValidation doCheckCorpsecret(@QueryParameter String value)
                throws IOException, ServletException {
            if (value.length() == 0)
                return FormValidation.error("NotNull");

            return FormValidation.ok();
        }

        public FormValidation doCheckAgentid(@QueryParameter String value)
                throws IOException, ServletException {
            if (value.length() == 0)
                return FormValidation.error("NotNull");
            if (!value.matches("[0-9]+"))
                return FormValidation.error("Invalid Number");

            return FormValidation.ok();
        }

        public FormValidation doCheckAccountBindings(@QueryParameter String value)
                throws IOException, ServletException {
            if (value.length() == 0)
                return FormValidation.error("NotNull");

            return FormValidation.ok();
        }

        public FormValidation doCheckRecipients(@QueryParameter String value)
                throws IOException, ServletException {
            if (!value.matches("[a-zA-Z0-9]+[,a-zA-Z0-9]+"))
                return FormValidation.error("Invalid Recipient List");

            return FormValidation.ok();
        }

        @Override
        public boolean isApplicable(Class<? extends AbstractProject> aClass) {
            return true;
        }

        @Override
        public String getDisplayName() {
            return "Subversion Committer Notification";
        }

    }

    void sendWorkWeiXinMessage(List<String> authors, String content) {
        String access_token = this.authorization.getToken();

        String url = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=" + access_token;

//        StringBuilder toUserBuilder = new StringBuilder();
//        for (int idx = 0, loop = authors.size(); idx < loop; idx++) {
//            toUserBuilder.append(authors.get(idx));
//
//            if (loop - idx > 1) {
//                toUserBuilder.append("|");
//            }
//        }

        WorkWeiXinMessage message = new WorkWeiXinMessage("@all", this.agentid, content);

        httpPost(url, message, true);
    }

    private static class WorkWeiXinMessage {
        private String touser;
        private String msgtype = "text";
        private Integer agentid;
        private WorkWeiXinMessageContent text;
        private Integer safe = 0;

        public String getTouser() {
            return touser;
        }

        public String getMsgtype() {
            return msgtype;
        }

        public Integer getAgentid() {
            return agentid;
        }

        public WorkWeiXinMessageContent getText() {
            return text;
        }

        public Integer getSafe() {
            return safe;
        }

        public WorkWeiXinMessage(String touser, Integer agentid, String content) {
            this.touser = touser;
            this.agentid = agentid;
            this.text = new WorkWeiXinMessageContent(content);
        }

        private static class WorkWeiXinMessageContent {
            public String content;

            public WorkWeiXinMessageContent(String content) {
                this.content = content;
            }

            public String toString() {
                return content;
            }
        }
    }

//    private String getWorkWeiXinToken(String corpid, String corpsecret) {
//        final String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=" + corpid + "&corpsecret=" + corpsecret;
//
//        Request request = new Request.Builder().url(url).get().build();
//
//        try {
//            OkHttpClient client = new OkHttpClient();
//            Response response = client.newCall(request).execute();
//            log.debug("Invocation of webhook {} successful", url);
//            JSONObject jsonResp = JSON.parseObject(response.body().string());
//            return jsonResp.getString("access_token");
//        } catch (Exception e) {
//            log.info("Invocation of webhook {} failed", url, e);
//            return null;
//        }
//    }

    private void httpPost(String url, Object object, Boolean payloadIsJson) {
        String jsonString = JSON.toJSONString(object);
        RequestBody body;

        if (payloadIsJson) {
            body = RequestBody.create(PayloadType.JSON.getMediaType(), jsonString);
        } else {
            List params = generateParams(jsonString);
            body = RequestBody.create(PayloadType.STRING.getMediaType(), URLEncodedUtils.format(params, "ascii"));
        }

        Request request = new Request.Builder().url(url).post(body).build();
        try {
            OkHttpClient client = new OkHttpClient();
            Response response = client.newCall(request).execute();
            log.info("Invocation of webhook {} successful, response: {}", url, response.body().string());
        } catch (Exception e) {
            log.info("Invocation of webhook {} failed", url, e);
        }
    }

    private List generateParams(String jsonString) {
        ObjectMapper mapper = new ObjectMapper();
        Map<String, String> map = new LinkedHashMap<>();
        try {
            map = mapper.readValue(jsonString, new TypeReference<Map<String, String>>() {
            });
        } catch (IOException e) {
            e.printStackTrace();
        }

        List<NameValuePair> params = new LinkedList<>();
        for (Map.Entry<String, String> entry : map.entrySet()) {
            params.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
        }
        return params;
    }

    private enum PayloadType {
        JSON(MediaType.parse("application/json; charset=utf-8")),
        STRING(MediaType.parse("application/x-www-form-urlencoded; charset=utf-8"));

        private MediaType mediaType;

        PayloadType(MediaType mediaType) {
            this.mediaType = mediaType;
        }

        public MediaType getMediaType() {
            return mediaType;
        }
    }
}